package org.dlow.config;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.dlow.annotation.RpcService;
import org.dlow.common.Cache;
import org.dlow.common.URL;
import org.dlow.filter.FilterFactory;
import org.dlow.invoke.MethodExecutorFactory;
import org.dlow.register.RegistryProviderFactory;
import org.dlow.register.ServiceRegistry;
import org.dlow.socket.serialization.SerializationFactory;
import org.dlow.socket.server.Server;
import org.dlow.utils.IpUtil;
import org.dlow.utils.ServiceNameBuilder;

/**
 * ProviderPostProcessor - RPC服务端Bean后置处理器
 * 
 * <p>这是RPC服务端与SpringBoot集成的核心类,实现了Spring的BeanPostProcessor和InitializingBean接口。
 * 负责在SpringBoot应用启动时初始化RPC服务端组件,扫描并注册所有带@RpcService注解的服务。
 * 
 * <h3>核心职责:</h3>
 * <ul>
 *   <li><b>初始化RPC服务端组件</b>: 在Spring容器初始化完成后,加载各种SPI扩展(注册中心、调用器、过滤器等)</li>
 *   <li><b>启动Netty服务端</b>: 以守护线程方式启动网络通信服务端,监听客户端请求</li>
 *   <li><b>扫描@RpcService注解</b>: 扫描所有Bean,找到带有@RpcService注解的服务实现类</li>
 *   <li><b>注册服务到注册中心</b>: 将服务信息(IP、端口、服务名、版本号)注册到注册中心</li>
 *   <li><b>缓存服务实例</b>: 将服务实例缓存到本地Map,供后续RPC调用使用</li>
 * </ul>
 * 
 * <h3>工作流程:</h3>
 * <pre>
 * 1. SpringBoot启动
 *    ↓
 * 2. 执行afterPropertiesSet()方法
 *    ├─ 初始化注册中心工厂(RegistryProviderFactory)
 *    ├─ 初始化服务端过滤器(FilterFactory)
 *    ├─ 初始化方法调用器(ExecutorFactory)
 *    ├─ 初始化序列化工厂(SerializationFactory)
 *    └─ 启动Netty服务端(守护线程,solo方式)
 *    ↓
 * 3. Spring创建各个Bean
 *    ↓
 * 4. 执行postProcessBeforeInitialization()方法
 *    ├─ 检查Bean是否带有@RpcService注解
 *    ├─ 解析服务接口名称(默认第一个接口或指定接口)
 *    ├─ 构造服务URL(包含IP、端口、服务名、版本号)
 *    ├─ 将服务URL注册到注册中心
 *    └─ 将服务实例缓存到本地Map(key=服务名:版本号)
 *    ↓
 * 5. 服务端就绪,等待客户端RPC调用
 * </pre>
 * 
 * <h3>使用示例:</h3>
 * <pre>
 * // 实现服务接口
 * &#64;Component
 * &#64;RpcService
 * public class UserServiceImpl implements UserService {
 *     
 *     &#64;Override
 *     public User getUserById(Long id) {
 *         // 业务逻辑...
 *         return user;
 *     }
 * }
 * 
 * // 启动类
 * &#64;SpringBootApplication
 * &#64;EnableProviderRpc
 * public class ProviderApplication {
 *     public static void main(String[] args) {
 *         SpringApplication.run(ProviderApplication.class, args);
 *     }
 * }
 * </pre>
 * 
 * <h3>服务调用流程:</h3>
 * <pre>
 * 客户端请求到达
 *    ↓
 * Netty服务端接收
 *    ↓
 * 反序列化请求
 *    ↓
 * 解析服务名和版本号
 *    ↓
 * 从缓存Map获取服务实例 (Cache.SERVICE_MAP.get("服务名:版本号"))
 *    ↓
 * 通过反射调用方法
 *    ↓
 * 序列化响应结果
 *    ↓
 * 返回给客户端
 * </pre>
 * 
 * <h3>守护线程方式启动Netty:</h3>
 * <p>Netty服务端运行在守护线程中,采用solo方式:
 * <ul>
 *   <li><b>不阻塞主线程</b>: SpringBoot主线程可以继续初始化其他组件</li>
 *   <li><b>随主线程退出</b>: 当JVM退出时,守护线程会自动退出</li>
 *   <li><b>持续监听</b>: 服务端可以持续监听客户端请求</li>
 * </ul>
 * 
 * @description: 服务端Bean后置处理器,负责初始化RPC服务端并注册服务
 * @Author: 山沐与山
 * @CreateTime: 2024-05-17 14:58
 * @see BeanPostProcessor Spring Bean后置处理器接口
 * @see InitializingBean Spring初始化接口
 * @see RpcService 服务发布注解
 */
public class ProviderPostProcessor implements InitializingBean, BeanPostProcessor {

    private RpcProperties rpcProperties;

    private final String ip = IpUtil.getIP();

    public ProviderPostProcessor(RpcProperties rpcProperties) {
        this.rpcProperties = rpcProperties;
    }

    /**
     * Bean属性设置完成后的初始化方法
     * 
     * <p>此方法在Spring容器完成依赖注入后自动调用,负责初始化RPC服务端的所有组件。
     * 初始化顺序很重要,确保各组件的依赖关系正确。
     * 
     * <h3>初始化步骤:</h3>
     * <ol>
     *   <li>初始化注册中心工厂: 加载SPI配置的注册中心实现(Zookeeper等)</li>
     *   <li>初始化服务端过滤器: 加载用户自定义的服务端过滤器</li>
     *   <li>初始化方法调用器: 加载SPI配置的反射调用实现(JDK反射等)</li>
     *   <li>初始化序列化工厂: 加载SPI配置的序列化实现(JDK、Hessian等)</li>
     *   <li>启动Netty服务端: 以守护线程方式启动,监听客户端请求</li>
     * </ol>
     * 
     * <h3>为什么使用守护线程(daemon thread)?</h3>
     * <ul>
     *   <li><b>不阻塞主线程</b>: 如果使用普通线程,会阻塞SpringBoot的启动流程</li>
     *   <li><b>随主线程退出</b>: 当应用关闭时,守护线程会自动退出,无需手动关闭</li>
     *   <li><b>solo方式</b>: 单独启动一个线程运行Netty服务端,与SpringBoot主流程隔离</li>
     * </ul>
     * 
     * @throws Exception 初始化过程中可能抛出的异常
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        // 1. 初始化注册中心工厂,加载Zookeeper等注册中心实现
        RegistryProviderFactory.init();
        // 2. 初始化服务端过滤器,加载用户自定义的前置/后置过滤器
        FilterFactory.initServer();
        // 3. 初始化方法调用器,加载JDK反射等调用实现
        ExecutorFactory.init();
        // 4. 初始化序列化工厂,加载JDK、Hessian等序列化实现
        SerializationFactory.init();
        
        // 5. 启动Netty服务端(守护线程方式)
        Thread t = new Thread(() -> {
            final Server server = new Server(rpcProperties.getPort());
            try {
                // 启动服务端,开始监听客户端请求
                // 此处会阻塞当前线程,但由于在单独的线程中,不影响主流程
                server.run();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        // 设置为守护线程,随主线程退出而退出
        t.setDaemon(true);
        // 启动线程
        t.start();
    }


    /**
     * Bean初始化前的后置处理方法
     * 
     * <p>这是整个RPC服务端与SpringBoot集成的核心方法。
     * 在每个Bean初始化之前,检查其是否带有@RpcService注解,
     * 如果有,则将该服务注册到注册中心,并缓存服务实例供后续RPC调用使用。
     * 
     * <h3>处理流程:</h3>
     * <pre>
     * 对于每个Bean:
     *   检查是否有@RpcService注解?
     *     ↓ 是
     *   解析服务接口名称
     *     ├─ 如果指定了serviceInterface,使用指定的接口
     *     └─ 否则使用第一个实现的接口
     *     ↓
     *   构造服务URL
     *     ├─ IP: 本机IP地址
     *     ├─ Port: RPC服务端口
     *     ├─ ServiceName: 服务接口全限定名
     *     └─ Version: 服务版本号
     *     ↓
     *   注册到注册中心
     *     ├─ 在Zookeeper创建临时节点
     *     └─ 节点路径: /rpc/服务名/版本号/IP:端口
     *     ↓
     *   缓存服务实例
     *     ├─ Key: 服务名:版本号
     *     └─ Value: Bean实例
     *     ↓
     *   完成,该服务可以被远程调用了
     * </pre>
     * 
     * <h3>为什么要缓存服务实例?</h3>
     * <p>当客户端发起RPC请求时:
     * <ol>
     *   <li>Netty服务端接收到请求</li>
     *   <li>解析出服务名和版本号</li>
     *   <li>构造key: 服务名:版本号</li>
     *   <li>从Cache.SERVICE_MAP.get(key)获取服务实例</li>
     *   <li>通过反射调用服务实例的方法</li>
     *   <li>返回结果给客户端</li>
     * </ol>
     * 
     * <h3>服务接口解析规则:</h3>
     * <ul>
     *   <li>如果@RpcService指定了serviceInterface,使用指定的接口</li>
     *   <li>否则,默认使用实现类的第一个接口</li>
     *   <li>例如: UserServiceImpl implements UserService, Serializable</li>
     *   <li>如果不指定serviceInterface,会使用UserService</li>
     *   <li>如果只想发布UserService,建议显式指定避免歧义</li>
     * </ul>
     * 
     * @param bean 正在初始化的Bean实例
     * @param beanName Bean的名称
     * @return 处理后的Bean实例
     * @throws BeansException 处理过程中的异常
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 1. 获取Bean的Class对象
        Class<?> beanClass = bean.getClass();
        
        // 2. 检查Bean是否带有@RpcService注解
        RpcService rpcService = beanClass.getAnnotation(RpcService.class);
        if (rpcService != null) {
            // 3. 解析服务接口名称
            String serviceName;
            if (!rpcService.serviceInterface().equals(void.class)){
                // 如果@RpcService指定了serviceInterface,使用指定的接口
                serviceName = rpcService.serviceInterface().getName();
            } else {
                // 否则,默认使用实现类的第一个接口
                // 注意: 如果实现了多个接口,可能不是你想要的接口,建议显式指定
                serviceName = beanClass.getInterfaces()[0].getName();
            }
            
            try {
                // 4. 获取注册中心服务(根据配置文件指定的类型,如zookeeper)
                ServiceRegistry registryService = RegistryProviderFactory.get(rpcProperties.getRegistry().getName());
                
                // 5. 构造服务URL,包含服务的所有元信息
                final URL url = new URL();
                url.setPort(Properties.getPort());           // RPC服务端口
                url.setIp(ip);                               // 本机IP地址
                url.setServiceName(serviceName);             // 服务接口全限定名
                url.setVersion(rpcService.version());        // 服务版本号
                
                // 6. 将服务注册到注册中心
                // 在Zookeeper中创建临时节点: /rpc/服务名/版本号/IP:端口
                // 当服务下线时,临时节点会自动删除
                registryService.registerService(url);
                
                // 7. 缓存服务实例到本地Map
                // key格式: 服务名:版本号,如"org.dlow.service.UserService:1.0"
                final String key = ServiceNameBuilder.buildServiceKey(serviceName, rpcService.version());
                // value是Bean实例,后续RPC调用时会从这里获取
                Cache.SERVICE_MAP.put(key,bean);
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        // 8. 返回Bean实例(未修改)
        return bean;
    }
}
